#include "Renderer_PCH.h"
#include "MaterialMgr.h"

/*--------------------------------------------------------------------------*/

MaterialMgr::MaterialMgr() :
	m_material(NULL),
	m_activeBlendState(NULL),
	m_activeDepthstencilState(NULL),
	m_activeRasterizerState(NULL),
	m_constantOffset(0)
{

}

/*--------------------------------------------------------------------------*/

MaterialMgr::~MaterialMgr()
{

}

/*--------------------------------------------------------------------------*/

void MaterialMgr::InitMaterialMgr()
{
	m_constantBuffer = g_Renderer->GetRendererStorageMgr()->CreateBuffer("ConstantBuffer", CONST_BUFFER_SIZE, true, GL_UNIFORM_BUFFER);
}

/*--------------------------------------------------------------------------*/

void MaterialMgr::Push()
{
	__Check(m_stack.size() < MAX_MATERIAL_STACK_SIZE);
	m_stack.push(m_material);
}

/*--------------------------------------------------------------------------*/

void MaterialMgr::Pop()
{
	__Check(m_stack.empty() == false);
	const RMaterial* material = m_stack.top();
	m_stack.pop();

	Bind(material);
}

/*--------------------------------------------------------------------------*/

void MaterialMgr::Bind(const RBlendState* state )
{
	__Check(state);

	if (state != m_activeBlendState)
	{
		m_activeBlendState = state;
		m_activeBlendState->Bind();
	}
}

/*--------------------------------------------------------------------------*/

void MaterialMgr::Bind(const RRasterizerState* state )
{
	__Check(state);

	if (state != m_activeRasterizerState)
	{
		m_activeRasterizerState = state;
		m_activeRasterizerState->Bind();
	}
}

/*--------------------------------------------------------------------------*/

void MaterialMgr::Bind(const RDepthstencilState* state )
{
	__Check(state);

	if (state != m_activeDepthstencilState)
	{
		m_activeDepthstencilState = state;
		m_activeDepthstencilState->Bind();
	}
}

/*--------------------------------------------------------------------------*/

void MaterialMgr::Bind(const RMaterial* material )
{
	if (material == NULL)
	{
		return;
	}

	m_material = material;

	Bind(material->GetBlendState());
	Bind(material->GetDepthstencilState());
	Bind(material->GetRasterizerState());

	glUseProgram(material->GetProgramHandle());

	uint numSamplers = material->GetNumSamplers();
	for (uint i = 0; i < numSamplers; ++i)
	{
		const RTexture* texture = material->GetTexture(i);
		__Check(texture);

		const RSamplerState* sampler = material->GetSamplerState(i);
		__Check(sampler);

		uint location = material->GetLocation(i);

		if (location != -1)
		{
			__Check(location != UINT_MAX);

			glActiveTexture(GL_TEXTURE0 + i);
			glBindTexture(GL_TEXTURE_2D, texture->GetHandle());
			glBindSampler(i, sampler->GetHandle());

			glUniform1i(location, i);
		}
	}

	for (auto iter = material->m_constsValue.begin(); iter != material->m_constsValue.end(); ++iter)
	{
		g_Renderer->SetEngineUniform(iter->m_type, (void*) iter->m_value);
	}

	uint align = g_Renderer->GetOpenGLConstant(EOpenGLConstant::uniform_buffer_offset_alignment);
	uint vOffset = ALIGN(m_constantOffset, align);
	uint vSize = material->m_sizeofVconsts;

	uint gOffset = ALIGN(vOffset + vSize, align);
	uint gSize = material->m_sizeofGconsts;

	uint fOffset = ALIGN(gOffset + gSize, align);
	uint fSize = material->m_sizeofFconsts;

	m_constantOffset = fSize + fOffset;

	if (fOffset + fSize >= CONST_BUFFER_SIZE)
	{
		m_constantOffset = 0;

		vOffset = ALIGN(m_constantOffset, align);
		vSize = material->m_sizeofVconsts;

		gOffset = ALIGN(vOffset + vSize, align);
		gSize = material->m_sizeofGconsts;

		fOffset = ALIGN(gOffset + gSize, align);
		fSize = material->m_sizeofFconsts;
	}
	
	material->UpdateConstants(vOffset, gOffset, fOffset, m_constantBuffer);
}

/*--------------------------------------------------------------------------*/

void MaterialMgr::SetSampler( const std::string& name, RTexture* texture, RSamplerState* sampler )
{
	auto m_samplers = m_material->m_samplers;

	for (uint i = 0; i < m_samplers.size(); ++i)
	{
		RSampler& matsmp = m_samplers[i];

		if (matsmp.m_name == name)
		{
			if (texture)
			{
				glActiveTexture(GL_TEXTURE0 + i);
				glBindTexture(GL_TEXTURE_2D, texture->GetHandle());
				glUniform1i(matsmp.m_location, i);
			}

			if (sampler)
			{
				glBindSampler(i, sampler->GetHandle());
			}

			return;
		}
	}

	__Check(false);
}

/*--------------------------------------------------------------------------*/

static uint hash(const MaterialDesc& m)
{
	uchar* str = new uchar[sizeof(MaterialDesc)];
	uchar* mat = (uchar*) &m;

	for (uint i = 0; i < sizeof(MaterialDesc); ++i)
	{
		if (!mat[i])
		{
			str[i] = 255;
		}
		else
		{
			str[i] = mat[i];
		}
	}

	str[sizeof(MaterialDesc) - 1] = 0;

	delete [] str;

	std::hash<const char *> hasher;
	return hasher((const char*) str);
}

/*--------------------------------------------------------------------------*/

static void DeleteMacro(std::vector<RMacro>& macros, const std::string& name)
{
	for (auto iter = macros.begin(); iter != macros.end();)
	{
		if (iter->GetName() == name)
		{
			iter = macros.erase(iter);
		}
		else
		{
			++iter;
		}
	}
}

/*--------------------------------------------------------------------------*/

const RMaterial* MaterialMgr::GetMaterialFromMaster( const MaterialDesc& desc )
{
	std::string matname = "master_" + std::to_string(rand());
	RMaterial* material = g_Renderer->GetRendererStorageMgr()->CreateMaterial(matname);

	if (material == NULL)
	{
		return g_Renderer->GetRendererStorageMgr()->GetMaterial(matname);
	}
	
	material->m_blendstate			= desc.m_blendstate ? desc.m_blendstate : g_Renderer->GetRendererStorageMgr()->GetBlendState("default");
	material->m_depthstencilstate	= desc.m_depthstencilstate ? desc.m_depthstencilstate : g_Renderer->GetRendererStorageMgr()->GetDepthstencilState("default");
	material->m_rasterizerstate		= desc.m_rasterizerstate ? desc.m_rasterizerstate : g_Renderer->GetRendererStorageMgr()->GetRasterizerState("default");

	if (desc.m_flags & EMaterialDesc::UseBumpmapping)
	{
		material->m_macros.push_back(RMacro("USE_BUMPMAPPING", "1"));
		material->m_samplers.push_back(	RSampler(	"normal0",
													desc.m_Bumpmapping_normal0Sampler ? desc.m_Bumpmapping_normal0Sampler : g_Renderer->GetRendererStorageMgr()->GetSamplerState("default"),
													desc.m_Bumpmapping_normal0 ? desc.m_Bumpmapping_normal0 : g_Renderer->GetRendererStorageMgr()->GetTexture("default"), 
													-1));

		material->m_constsValue.push_back(RConstantValue(EEngineUniform::Bumpmapping_Strength_1f, (void*) &desc.m_Bumpmapping_Strength));
		material->m_constsValue.push_back(RConstantValue(EEngineUniform::Bumpmapping_TextureMultiplier_1f, (void*) &desc.m_Bumpmapping_TextureMultiplier));
	}

	material->m_macros.push_back(RMacro("USE_POSITION0", "1"));
	if (desc.m_flags & EMaterialDesc::UseInstancing)
	{
		material->m_macros.push_back(RMacro("USE_INSTANCING", "1"));
	}
	else
	{
		material->m_macros.push_back(RMacro("USE_UNIFORM_MODELVIEWPROJECTIONMATRIX", "1"));
	}

	material->m_macros.push_back(RMacro("USE_FRAGCOLOR0", "1"));

	if (desc.m_flags & EMaterialDesc::ColorAsColor0)
	{
		material->m_macros.push_back(RMacro("COLOR0_AS_CLR", "1"));
		material->m_macros.push_back(RMacro("USE_COLOR0", "1"));
	}
	else if (desc.m_flags & EMaterialDesc::ColorAsTexture0)
	{
		material->m_macros.push_back(RMacro("TEX0_AS_CLR", "1"));
		material->m_samplers.push_back(	RSampler(	"texture0",
													desc.m_texture0Sampler ? desc.m_texture0Sampler : g_Renderer->GetRendererStorageMgr()->GetSamplerState("default"),
													desc.m_texture0 ? desc.m_texture0 : g_Renderer->GetRendererStorageMgr()->GetTexture("default"), 
													-1));
	}
	else if (desc.m_flags & EMaterialDesc::ColorAsConstant)
	{
		material->m_macros.push_back(RMacro("CONSTANT_AS_CLR", "1"));
		material->m_macros.push_back(RMacro("USE_UNIFORM_CONSTANTCOLOR", "1"));

		material->m_constsValue.push_back(RConstantValue(EEngineUniform::ConstantColor_4f, (void*) &desc.m_color));
	}

	if (desc.m_flags & EMaterialDesc::OutNormal)
	{
		material->m_macros.push_back(RMacro("USE_FRAGCOLOR1", "1"));

		material->m_macros.push_back(RMacro("USE_UNIFORM_MODELMATRIX", "1"));
		material->m_macros.push_back(RMacro("NORMAL0_WS_AS_NRM", "1"));
	}

	if (desc.m_flags & EMaterialDesc::OutMaterial)
	{
		material->m_macros.push_back(RMacro("USE_FRAGCOLOR2", "1"));
	}


	material->CreateProgram("#include \"master_vs.glsl\"\n", "\n\n", "#include \"master_fs.glsl\"\n");

// 	if (desc.m_flags & EMaterialDesc::UseFog)
// 	{
// 		material->m_macros.push_back(RMacro("USE_FOG", "1"))
// 	}

	if (desc.m_flags & EMaterialDesc::EnableInstancing)
	{
		RMaterial* imaterial = g_Renderer->GetRendererStorageMgr()->CreateMaterial(matname + "_instanced");

		imaterial->m_blendstate			= material->m_blendstate;
		imaterial->m_depthstencilstate	= material->m_depthstencilstate;
		imaterial->m_rasterizerstate	= material->m_rasterizerstate;

		//macro sampler constval
		for (auto iter = material->m_macros.begin(); iter != material->m_macros.end(); ++iter)
		{
			imaterial->m_macros.push_back(*iter);
		}

		for (auto iter = material->m_samplers.begin(); iter != material->m_samplers.end(); ++iter)
		{
			imaterial->m_samplers.push_back(*iter);
		}

		for (auto iter = material->m_constsValue.begin(); iter != material->m_constsValue.end(); ++iter)
		{
			imaterial->m_constsValue.push_back(*iter);
		}


		DeleteMacro(imaterial->m_macros, "USE_UNIFORM_MODELMATRIX");
		DeleteMacro(imaterial->m_macros, "USE_UNIFORM_MODELVIEWMATRIX");
		DeleteMacro(imaterial->m_macros, "USE_UNIFORM_MODELVIEWPROJECTIONMATRIX");

		imaterial->m_macros.push_back(RMacro("USE_INSTANCING", "1"));
		imaterial->m_macros.push_back(RMacro("USE_UNIFORM_VIEWPROJECTIONMATRIX", "1"));
		imaterial->CreateProgram("#include \"master_vs.glsl\"\n", "\n\n", "#include \"master_fs.glsl\"\n");

		imaterial->m_normalVersion = material;
		imaterial->m_instancedVersion = imaterial;

		material->m_instancedVersion = imaterial;
	}
	else
	{
		material->m_instancedVersion = NULL;
	}

	material->m_normalVersion = material;

	return material;
}

/*--------------------------------------------------------------------------*/
